using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using CrystalQuartz.Application;
using CrystalQuartz.AspNetCore;
using CrystalQuartz.Demo.Jobs;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Quartz;
using Quartz.Impl;
using Quartz.Logging;
using Quartz.Spi;

namespace CrystalQuartz.Demo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddQuartz();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            app.UseCookiePolicy();

            //
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            //
            //var scheduler = await ConfigJobs(app);
            var scheduler = ConfigJobs(app, applicationLifetime).Result;
            app.UseCrystalQuartz(() => scheduler);
            //
        }

        private async Task<IScheduler> ConfigJobs(IApplicationBuilder app, IApplicationLifetime applicationLifetime)
        {
            //LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());
            var properties = new NameValueCollection
            {
                ["quartz.scheduler.instanceName"] = "DefaultQuartzScheduler",
                //["quartz.scheduler.instanceId"] = "instance_one",
                //["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz",
                //["quartz.threadPool.threadCount"] = "40",
                //["quartz.threadPool.threadPriority"] = "Normal",
                ////
                ["quartz.plugin.xml.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins",
                ["quartz.plugin.xml.fileNames"] = "~/quartz_jobs.xml",

                //CrystalQuartz Asp.net Core 不支持Remote
                //["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz",
                //["quartz.scheduler.exporter.port"] = "555",
                //["quartz.scheduler.exporter.bindName"] = "QuartzScheduler",
                //["quartz.scheduler.exporter.channelType"] = "tcp",
                //["quartz.scheduler.exporter.channelName"] = "httpQuartz",
                //
                ["quartz.jobStore.misfireThreshold"] = "60000",
                ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
                ["quartz.jobStore.useProperties"] = "false",
                ["quartz.jobStore.dataSource"] = "default",
                ["quartz.jobStore.tablePrefix"] = "QRTZ_",
                ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
                ["quartz.dataSource.default.connectionString"] = "Data Source=.;Initial Catalog=Quartz;Persist Security Info=True;User ID=sa;password=xxxxxx;MultipleActiveResultSets=True",
                ["quartz.dataSource.default.provider"] = "SqlServer",
                ["quartz.serializer.type"] = "json",
            };

            var factory = new StdSchedulerFactory(properties);
            var scheduler = await factory.GetScheduler();
            //scheduler.JobFactory = app.ApplicationServices.GetService<IJobFactory>(); 

            //
            var printJob = JobBuilder.Create<PrintMessageJob>()
                .WithIdentity("PrintMessage", "Default")
                .Build();
            var helloJob = JobBuilder.Create<HelloQuartzJob>()
               .WithIdentity("HelloQuartz", "MyJob")
               .Build();

            var trigger = TriggerBuilder.Create()
                .WithIdentity("trigger1", "MyGroup")
                //.ForJob(printJob)
                .StartNow()
                .WithSimpleSchedule(x => x.WithIntervalInSeconds(30).RepeatForever())
                .Build();

            var trigger2 = TriggerBuilder.Create()
                .WithIdentity("trigger2", "MyGroup")
                //.ForJob(printJob)
                .StartNow()
                .WithSimpleSchedule(x => x.WithIntervalInSeconds(60).RepeatForever())
                .Build();

            var trigger3 = TriggerBuilder.Create()
                .WithIdentity("trigger3", "default")
                .ForJob(printJob)
                .StartNow()
                .WithCronSchedule("0 /1 * ? * *")
                .Build();

            await scheduler.ScheduleJob(printJob, new[] { trigger, trigger2, trigger3 }, true);
            await scheduler.ScheduleJob(helloJob, new[] { trigger2 }, true);
            applicationLifetime.ApplicationStopped.Register(async () => await scheduler.Shutdown());
            await scheduler.Start();
            //
            
            return scheduler;
        }


        public class ConsoleLogProvider : ILogProvider
        {
            public Logger GetLogger(string name)
            {
                return (logLevel, messageFunc, exception, formatParameters) => {

                    if (messageFunc != null)
                    {
                        Console.WriteLine($"{logLevel} {messageFunc?.Invoke()}", formatParameters);

                    }
                    if(exception != null)
                    {
                        Console.WriteLine(exception.ToString());
                    }
                    return true;

                };
            }

            public IDisposable OpenMappedContext(string key, string value)
            {
                throw new NotImplementedException();
            }

            public IDisposable OpenNestedContext(string message)
            {
                throw new NotImplementedException();
            }
        }

        //private static IScheduler CreateScheduler()
        //{
        //    var schedulerFactory = new StdSchedulerFactory();
        //    var scheduler = schedulerFactory.GetScheduler().Result;

        //    var job = JobBuilder.Create<PrintMessageJob>()
        //        .WithIdentity("localJob", "default")
        //        .Build();

        //    var trigger = TriggerBuilder.Create()
        //        .WithIdentity("trigger1", "default")
        //        .ForJob(job)
        //        .StartNow()
        //        .WithCronSchedule("0 /1 * ? * *")
        //        .Build();

        //    scheduler.ScheduleJob(job, trigger);

        //    scheduler.Start();

        //    return scheduler;
        //}
    }


    public class QuartzJobFactory : IJobFactory
    {
        private readonly IServiceProvider _serviceProvider;
        private readonly ILogger<QuartzJobFactory> _logger;
        public QuartzJobFactory(IServiceProvider serviceProvider, ILogger<QuartzJobFactory> logger)
        {
            _serviceProvider = serviceProvider;
            _logger = logger;
        }

        /// <summary>
        /// create job
        /// </summary>
        /// <param name="bundle"></param>
        /// <param name="scheduler"></param>
        /// <returns></returns>
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            if (_serviceProvider == null) return null;
            var jobDetail = bundle.JobDetail;
            try
            {
                return _serviceProvider.GetService(jobDetail.JobType) as IJob;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Create Job({jobDetail.JobType}) instance Error.");
                //throw;
            }
            return null;
            //return Activator.CreateInstance(jobDetail.JobType) as IJob;
        }

        /// <summary>
        /// clean Job
        /// </summary>
        /// <param name="job"></param>
        public void ReturnJob(IJob job)
        {
            //when use JobPooling could implement ReturnJob
            (job as IDisposable)?.Dispose();
        }
    }

    public static class QuartzServiceCollectionExtensions
    {

        public static IServiceCollection AddQuartz(this IServiceCollection services)
        {
            var jobs = Assembly.GetCallingAssembly().GetTypes()
                .Where(t => t.IsClass && !t.IsAbstract && typeof(IQuartzJob).IsAssignableFrom(t));
            foreach (var job in jobs)
            {
                services.AddTransient(job);
            }
            //
            services.AddSingleton<IJobFactory, QuartzJobFactory>();
            //services.addt
            return services;
        }
    }

    public static class QuartzApplicationBuilderExtensions
    {

    }

}
